﻿using System;
using System.Collections.Generic;
using System.Linq;
using SharedTypes;

namespace GeneticAlgorithm
{
    /// <summary>
    /// Популяция. Содержит набор хромосом и методы перехода
    /// между поколениями а так же очистки популяции.
    /// </summary>
    public class Population
    {
        #region Constants
        /// <summary>
        /// Вероятность мутации.
        /// </summary>
        private const double MutationProbability = 0.5d;

        /// <summary>
        /// Вероятсность скрещивания.
        /// </summary>
        private const double CrossProbability = 0.5d;
        #endregion // Constants

        #region Members
        /// <summary>
        /// Текущий алгоритм исполнительного файла.
        /// </summary>
        private IAlgorithm currAlg;

        /// <summary>
        /// Контейнер популяции.
        /// </summary>
        private IList<Chromosome> population;

        /// <summary>
        /// Счётчик поколения.
        /// </summary>
        private int generation;

        /// <summary>
        /// Размер популяции.
        /// </summary>
        private int populationSize;

        /// <summary>
        /// Результат, полученный на рекомендуемых настройках.
        /// </summary>
        private double recommendedResult;

        /// <summary>
        /// Результат, полученный с текущими настройками
        /// </summary>
        private double manualResult;

        /// <summary>
        /// Генератор случайных чисел.
        /// </summary>
        private Random randomiser = new Random(DateTime.Now.Millisecond); 

        /// <summary>
        /// Делегат, описывающий функцию перехода к следующему поколению.
        /// </summary>
        /// <param name="currentGeneration">Номер текущего поколения.</param>
        public delegate void GenerationChangeHandler(int currentGeneration);
        #endregion //  Members

        #region Properties
        /// <summary>
        /// Результат, полученный на рекомендуемых настройках.
        /// </summary>
        public double ManualResult
        {
            get { return recommendedResult; }
            set { recommendedResult = value; }
        }

        /// <summary>
        /// Возвращает список параметров, при которых получен 
        /// наилучший результат в указанной сборке.
        /// </summary>
        public Chromosome GetBestResult
        {
            get { return population.First(); }
        }
        #endregion // Properties

        #region Constructor
        /// <summary>
        /// Конструктор.
        /// </summary>
        /// <param name="size">Размер популяции.</param>
        /// <param name="currentAlg">Объект алгоритма исполнительного файла.</param>
        /// <param name="result">Размер полученного архива при настройках,
        /// предоставленных производителем (по умолчанию).</param>
        public Population(int size, IAlgorithm currentAlg, IDictionary<int, LinkedParameter> parameters, double result)
        {
            generation = 0;
            populationSize = size;
            ManualResult = recommendedResult = result;
            currAlg = currentAlg;
            population = new List<Chromosome>(populationSize);
            Chromosome tmp;
            for (int i = 0; i < populationSize; i++)
            {
                tmp = new Chromosome(parameters);
                population.Add(Mutate(tmp));
            }
        }
        #endregion // Constructor;

        #region Methods
        /// <summary>
        /// Вычисляет "жизнеспособность" хромосомы.
        /// </summary>
        /// <param name="chromosom">Хромосома.</param>
        /// <returns>Жизнеспособность.</returns>
        public double GetFitness(Chromosome chromosom)
        {
            double currFitness = 0;
            try
            {
                long curCompression = currAlg.DoCompression(chromosom.Gens);
                if (curCompression > 0)
                {
                    currFitness = recommendedResult / curCompression;
                }
                else
                { 
                    currFitness = 0;
                }
                this.manualResult = curCompression;
            }
            catch (Exception e)
            {
                currFitness = 0;// ooops
            }
            return currFitness;
        }

        /// <summary>
        /// Оператор скрещивания.
        /// </summary>
        public Chromosome Cross(Chromosome parentOne, Chromosome parenTwo)
        {
            IList<int> keysOne = parentOne.Gens.Keys.ToList();
            int parametersNum = keysOne.Count;
            IList<int> keysSecond = parenTwo.Gens.Keys.ToList();

            IDictionary<int, LinkedParameter> parameters = new Dictionary<int, LinkedParameter>(parametersNum);
            for (int i = 0; i < parametersNum; i++)
            {
                if ((randomiser.Next() % 2) == 0)
                {
                    parameters.Add(i, parentOne.Gens[keysOne[i]]);
                }
                else
                {
                    parameters.Add(i, parenTwo.Gens[keysSecond[i]]);
                }
            }
            Chromosome newChromosome = new Chromosome(parameters);
            newChromosome.Fitness = GetFitness(newChromosome);
            newChromosome.Compression = this.manualResult;
            newChromosome.TimeElapsed = currAlg.TimeElapsed;
            return newChromosome;
        }

        /// <summary>
        /// Оператор мутации.
        /// </summary>
        /// <param name="toMutate">Оригинальная хромосома.</param>
        /// <returns>Мутировавшая хромосома.</returns>
        public Chromosome Mutate(Chromosome toMutate)
        {
            bool flag = true;
            while (flag)
            {
                foreach (KeyValuePair<int, LinkedParameter> param in toMutate.Gens)
                {
                    param.Value.parameter.PlaceValuesInRandomState();
                }
                toMutate.Fitness = GetFitness(toMutate);
                if (toMutate.Fitness != 0.0d)
                {
                    flag = false;
                }
            }
            toMutate.Compression = this.manualResult;
            toMutate.TimeElapsed = currAlg.TimeElapsed;
            return toMutate;
        }

        /// <summary>
        /// Получить массив параметров последнего поколения
        /// </summary>
        public IDictionary<int, LinkedParameter>[] GetLastGenParams()
        {
            IDictionary<int, LinkedParameter>[] param = new Dictionary<int, LinkedParameter>[population.Count];
            int i = 0;
            foreach(Chromosome pop in population)
            {
                param[i] = pop.Gens;
                i++;
            }
            return param;
        }

        /// <summary>
        /// Получить массив времени исполнительного файла
        /// </summary>
        public TimeSpan[] GetLastGenTime()
        {
            TimeSpan[] param = new TimeSpan[population.Count];
            int i = 0;
            foreach (Chromosome pop in population)
            {
                param[i] = pop.TimeElapsed;
                i++;
            }
            return param;
        }

        /// <summary>
        /// Получить массив степени исполнительного файла (размер результирующего архива)
        /// </summary>
        public double[] GetLastGenFitness()
        {
            double[] param = new double[population.Count];
            int i = 0;
            foreach (Chromosome pop in population)
            {
                param[i] = pop.Fitness;
                i++;
            }
            return param;
        }

        /// <summary>
        /// Переходит к следующему поколению.
        /// </summary>
        public void GoToNextGeneration()
        {
            for (int i = 0; i < populationSize; i++)
            {
                if (randomiser.NextDouble() < CrossProbability)
                {
                    population.Add(Cross(population[i], population[randomiser.Next(populationSize - 1)]));
                }
            }
            
            for (int i = 0; i < population.Count; i++)
            {
                if (population[i].Fitness < 1.0d && (randomiser.NextDouble() < MutationProbability))
                {
                    population[i] = Mutate(population[i]);
                }
            }

            population = GetSortedPopulation();
            RemoveRange(populationSize, population.Count - 1); // ?????????
            generation++;  
        }

        /// <summary>
        /// Удаляет хромосомы из популяции.
        /// </summary>
        /// <param name="startIndex">Стартовый индекс.</param>
        /// <param name="endIndex">Конечный индекс.</param>
        private void RemoveRange(int startIndex, int endIndex)
        {
            for (int i = endIndex; i > startIndex; i--)
            {
                population.RemoveAt(i);
            }
        }

        /// <summary>
        /// Сортирует хромосомы в популяции по убыванию 
        /// их жизнеспособности.
        /// </summary>
        /// <returns></returns>
        private IList<Chromosome> GetSortedPopulation()
        {
            var lst = from t in population 
                      orderby t.Fitness descending 
                      select t;
            return lst.ToList<Chromosome>();
        }
        #endregion // Methods
    }
}
